package cn.org.donald.framework.auth.filter;

import cn.org.donald.framework.auth.config.JWTToken;
import cn.org.donald.framework.auth.config.ShiroConfig;
import cn.org.donald.framework.constant.EncodeConstant;
import cn.org.donald.framework.constant.InfoConstant;
import cn.org.donald.framework.handler.ServiceException;
import com.auth0.jwt.exceptions.*;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

/**
 * @author ： Donald
 * @date ： 2020/10/18 23:09
 * @description：
 */
public class JwtFilter extends AccessControlFilter {

    private Logger logger = LoggerFactory.getLogger(JwtFilter.class);

    /**
     * 返回true直接放行
     *
     * @param servletRequest
     * @param servletResponse
     * @param o
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        //当主体不为null,且认证通过时放行
        return subject != null && subject.isAuthenticated();
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader(EncodeConstant.AUTHORIZATION_NAME);

        if ("OPTIONS".equals(request.getMethod())){ // 跨域请求第一次发送的是OPTIONS,并不会携带token,一律放行
            return true;
        }

        //如果客户端没携带token
        if (StringUtils.isEmpty(token)) {

            // 验证uri是否允许放行
            if (WhiteVerify(request)) return true;
            logger.debug("请求头无携带authorization");
            throw new AuthenticationException(InfoConstant.UNAUTHORIZED);
        }

        JWTToken jwtToken = new JWTToken(token);
        Subject subject = SecurityUtils.getSubject();

        try {

            subject.login(jwtToken);

        } catch (AlgorithmMismatchException exception) {
            exception.printStackTrace();
            throw new AuthenticationException("算法不匹配异常");
        } catch (SignatureVerificationException exception) {
            exception.printStackTrace();
            throw new AuthenticationException("签名验证异常");
        } catch (TokenExpiredException exception) {
            exception.printStackTrace();
            throw new AuthenticationException("token过期异常");
        } catch (InvalidClaimException exception) {
            exception.printStackTrace();
            throw new AuthenticationException("无效Claim异常");
        } catch (JWTDecodeException exception) {
            exception.printStackTrace();
            throw new AuthenticationException("JWT解码异常");
        } catch (JWTVerificationException exception) {
            exception.printStackTrace();
            throw new AuthenticationException("JWT验证异常");
        } catch (UnauthorizedException e) {
            throw new UnknownAccountException("权限不足");
        } catch (ServiceException e) {
            throw new ServiceException(InfoConstant.UNAUTHORIZED);
        }
        return true;
    }

    /**
     * 白名单认证
     * @param httpServletRequest
     * @return
     */
    public static boolean WhiteVerify(HttpServletRequest httpServletRequest) {
        String uri = httpServletRequest.getRequestURI();
        uri = uri.substring(1);
        if (uri.contains("/")) {
            String[] split = uri.split("/");
            if (split.length > 0) {
                String k = "";
                for (String s : split) {
                    k += "/"+s;
                    String config = ShiroConfig.getShiroConfigMap(k);
                    if (config == null) {
                        config = ShiroConfig.getShiroConfigMap(k + "/**");
                        if (config == null) continue;
                    }
                    if ("anon".equals(config)) return true;
                }
            }
        }
        return false;
    }
}
